package gin

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/testdata/protoexample"
	"log"
	"net/http"
	"strconv"
	"time"
)

// ProductVo 切记定义表单参数字段一定要以大写字母开头
type ProductVo struct {
	Name     string    `form:"name"`
	Price    int64     `form:"price"`
	Tag      string    `form:"tag"`
	Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

// GProduct curl --location --request GET '127.0.0.1/add_product?name=猪八戒&price=32&tag=zz' 可将请求参数绑定到某个struct实例中
func GProduct(c *gin.Context) {
	var prodVo ProductVo
	// 或者简单地使用 ShouldBind 方法自动绑定
	err := c.ShouldBind(&prodVo)
	// 你可以使用显式绑定声明绑定 multipart form
	//err = c.ShouldBindWith(&prodVo, binding.Form)
	if err != nil {
		log.Printf("get => prodVo:%+v\n", prodVo)
		c.JSON(http.StatusOK, gin.H{"err": err})
	} else {
		log.Printf("bind vo:%+v", prodVo)
		c.JSON(http.StatusOK, gin.H{"code": 0})
	}
}

// PFProduct curl --location --request POST '127.0.0.1/post_product/' --form 'name="孙武"' --form 'price="24"' --form 'tag="军事家"'
func PFProduct(c *gin.Context) {

	// post 获取 form-data 数据
	name := c.PostForm("name")
	price := c.PostForm("price")
	tag := c.PostForm("tag")

	priceInt, err := strconv.ParseInt(price, 10, 64)

	if err != nil {
		log.Printf("post price parse err:%v\n", err)
		c.JSON(http.StatusOK, gin.H{"err": err})
	} else {
		prodVo := &ProductVo{
			Name:  name,
			Price: priceInt,
			Tag:   tag,
		}
		log.Printf("bind vo:%+v", prodVo)
		c.JSON(http.StatusOK, gin.H{"code": 0, "data": prodVo})
	}
}

// GetJson 通常，JSON 使用 unicode 替换特殊 HTML 字符，例如 < 变为 \ u003c。如果要按字面对这些字符进行编码，则可以使用 PureJSON。Go 1.6 及更低版本无法使用此功能。
// GetJson GetPureJson 实际测试  两个函数返回的展现形式一致
func GetJson(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"html": "<b>Hello, world!</b>"})
}
func GetPureJson(c *gin.Context) {
	c.PureJSON(http.StatusOK, gin.H{"html": "<b>Hello, world!</b>"})
}

// PAXProduct curl --location --request POST '127.0.0.1/post_urlencoded/?name=xiaoming' --header 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'name=mack' --data-urlencode 'price=44' --data-urlencode 'tag=60'
func PAXProduct(c *gin.Context) {
	name := c.Query("name")      // xiaoming
	price := c.PostForm("price") // 44
	tag := c.PostForm("tag")     // 60

	c.JSON(http.StatusOK, gin.H{
		"name":  name,
		"price": price,
		"tag":   tag,
	})

}

func XML(c *gin.Context) {
	data := &ProductVo{
		Name:  "小明",
		Price: 45,
		Tag:   "学生",
	}
	c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK, "data": data})
}

func YAML(c *gin.Context) {
	data := &ProductVo{
		Name:  "小明",
		Price: 45,
		Tag:   "学生",
	}
	c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK, "data": data})
}

func ProtoBuf(c *gin.Context) {
	reps := []int64{int64(1), int64(2)}
	label := "test"
	data := &protoexample.Test{
		Label: &label,
		Reps:  reps,
	}
	c.ProtoBuf(http.StatusOK, data)
}

// 模拟一些私人数据
var secrets = gin.H{
	"sun":  gin.H{"email": "sun@bar.com", "phone": "123433"},
	"tom":  gin.H{"email": "tom@example.com", "phone": "666"},
	"jame": gin.H{"email": "jame@guapa.com", "phone": "523443"},
}

func BasicAuth(c *gin.Context) {
	user := c.MustGet(gin.AuthUserKey).(string)
	if secret, ok := secrets[user]; ok {
		c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
	} else {
		c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
	}
}

func Goroutine(c *gin.Context) {
	// 当在中间件或 handler 中启动新的 Goroutine 时，不能使用原始的上下文，必须使用只读副本。cCp := c.Copy()  还没有理解到为什么一定要用只读副本
	go func() {
		time.Sleep(5 * time.Second)
		log.Println("goroutine is done =>", c.Request.URL.Path)
	}()

	c.JSON(http.StatusOK, gin.H{"message": "success"})
}

// PostJson curl --location --request POST '127.0.0.1/post_json/' --header 'Content-Type: application/json' --data-raw '{"name":"qz_6667","price":336699,"tag":"336699"}'
func PostJson(c *gin.Context) {
	prodA := &ProductVo{}
	prodB := &ProductVo{}
	// c.ShouldBind 使用了 c.Request.Body，不可重用。
	if errA := c.ShouldBind(&prodA); errA == nil {
		c.String(http.StatusOK, `the body should be formA`)
		// 因为现在 c.Request.Body 是 EOF，所以这里会报错。
		// 如果想将请求参数绑定到多个变量中 使用 c.ShouldBindBodyWith(&objB, binding.JSON)
	} else if errB := c.ShouldBind(&prodB); errB == nil {
		c.String(http.StatusOK, `the body should be formB`)
	} else {

	}
	log.Printf("prodA:%+v, prodB:%+v\n", prodA, prodB)

	c.JSON(http.StatusOK, gin.H{"message": "success"})
}

// OpCookie curl --location --request GET '127.0.0.1/cookie/' --header 'Cookie: gin_cookie=qinzhe666;Path=/; Domain=localhost; '
// curl --location --request GET '127.0.0.1/cookie/'
func OpCookie(c *gin.Context) {
	cookie, err := c.Cookie("gin_cookie")
	if err != nil {
		log.Printf("cookie get err:%v", err)
		cookie = "NotSet"
		c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
	}
	log.Printf("cookie:%v", cookie)
	c.JSON(http.StatusOK, gin.H{"message": "success", "cookie": cookie})
}

func Redirect(c *gin.Context) {
	// 重定向到某个网址
	//c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
	// 重定向到某个路由
	c.Redirect(http.StatusFound, "/cookie/")
	// 修改上下文中的url 再使用 调用往下处理的函数
	//r.GET("/test", func(c *gin.Context) {
	//	c.Request.URL.Path = "/test2"
	//	r.HandleContext(c)
	//})
}
